using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace DecodeNavicatExportedPwd
{
    class Program
    {
        static void Main(string[] args)
        {
            string inputPath = string.Empty;
            string outputFormat = "txt";

            // 检查是否请求帮助
            if (args.Contains("--help") || args.Contains("-h") || args.Contains("/?") || args.Contains("/？") || args.Length == 0)
            {
                Console.WriteLine(@"
Navicat已保存连接密码解析工具

用法:
  DecodeNavicatExportedPwd [文件路径] [--format=txt|csv|json|md]

参数:
  文件路径         指定要解析的 .ncx 文件路径（支持单个文件或省略表示扫描当前目录下所有 .ncx）
  --format=格式     指定导出结果的格式，可选值: txt（默认）, csv, json, md
  --help, -h        显示此帮助信息

示例:
  DecodeNavicatExportedPwd myfile.ncx --format=csv
  DecodeNavicatExportedPwd --format=md     （扫描当前目录.ncx连接文件）

");
                return;
            }

            // 解析参数
            foreach (var arg in args)
            {
                if (arg.StartsWith("--format=", StringComparison.OrdinalIgnoreCase))
                {
                    outputFormat = arg.Substring("--format=".Length).ToLower();
                }
                else if (File.Exists(arg))
                {
                    inputPath = arg;
                }
            }

            string currentDir = Directory.GetCurrentDirectory();
            string dateFolder = DateTime.Now.ToString("yyyy-MM-dd");
            string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
            string logDir = Path.Combine(currentDir, "logs", dateFolder);
            Directory.CreateDirectory(logDir);

            string outputPath = Path.Combine(logDir, $"decrypted_passwords_{timestamp}.{outputFormat}");

            List<string> outputLines = new List<string>();
            var navicatPassword = new NavicatPassword();

            IEnumerable<string> files;
            if (!string.IsNullOrEmpty(inputPath))
            {
                files = new[] { inputPath };
            }
            else
            {
                files = Directory.GetFiles(currentDir, "*.ncx", SearchOption.AllDirectories);
            }

            foreach (var file in files)
            {
                try
                {
                    XDocument doc = XDocument.Load(file);
                    var connNode = doc.Descendants("Connection").FirstOrDefault();
                    if (connNode == null)
                    {
                        outputLines.Add($"[✘] {file} => 未找到 <Connection> 节点");
                        continue;
                    }

                    var pwdAttr = connNode.Attribute("Password");
                    if (pwdAttr == null || string.IsNullOrWhiteSpace(pwdAttr.Value))
                    {
                        outputLines.Add($"[✘] {file} => 未找到 Password 属性或为空");
                        continue;
                    }

                    string encryptedPwd = pwdAttr.Value;
                    string decrypted = navicatPassword.Decrypt(encryptedPwd);
                    outputLines.Add($"[✔] {file} => {decrypted}");
                }
                catch (Exception ex)
                {
                    outputLines.Add($"[✘] {file} => 解析失败: {ex.Message}");
                }
            }

            // 输出日志
            try
            {
                switch (outputFormat)
                {
                    case "txt":
                        File.WriteAllLines(outputPath, outputLines);
                        break;
                    case "csv":
                        File.WriteAllLines(outputPath, outputLines.Select(l =>
                        {
                            if (l.StartsWith("[✔]") || l.StartsWith("[✘]"))
                            {
                                var parts = l.Substring(4).Split("=>");
                                return $"\"{parts[0].Trim()}\",\"{parts[1].Trim()}\"";
                            }
                            return $"\"{l}\"";
                        }));
                        break;
                    case "json":
                        var jsonList = outputLines.Select(l => new
                        {
                            Status = l.StartsWith("[✔]") ? "Success" : "Fail",
                            File = l.Substring(4).Split("=>")[0].Trim(),
                            Message = l.Contains("=>") ? l.Split("=>")[1].Trim() : ""
                        });
                        File.WriteAllText(outputPath, System.Text.Json.JsonSerializer.Serialize(jsonList, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
                        break;
                    case "md":
                        var mdLines = new List<string>
                        {
                            "| File | Status | Password/Message |",
                            "|------|--------|------------------|"
                        };
                        foreach (var l in outputLines)
                        {
                            string status = l.StartsWith("[✔]") ? "✔" : "✘";
                            string[] parts = l.Substring(4).Split("=>");
                            string file = parts[0].Trim();
                            string msg = parts.Length > 1 ? parts[1].Trim() : "";
                            mdLines.Add($"| {file} | {status} | {msg} |");
                        }
                        File.WriteAllLines(outputPath, mdLines);
                        break;
                    default:
                        Console.WriteLine($"[!] 不支持的格式：{outputFormat}，请使用 txt/csv/json/md");
                        return;
                }

                Console.WriteLine($"\n✅ 处理完成，结果已保存到：{outputPath}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[✘] 写入日志失败：{ex.Message}");
            }

            Console.WriteLine("按任意键退出...");
            Console.ReadKey();
        }
    }

    public class NavicatPassword
    {
        private readonly byte[] aesKey = Encoding.ASCII.GetBytes("libcckeylibcckey");
        private readonly byte[] aesIv = Encoding.ASCII.GetBytes("libcciv libcciv ");
        private readonly byte[] blowKey;
        private readonly byte[] blowIv;
        private const string BlowString = "3DC5CA39";

        public NavicatPassword(int version = 12)
        {
            using (SHA1 sha = SHA1.Create())
            {
                this.blowKey = sha.ComputeHash(Encoding.ASCII.GetBytes(BlowString));
            }
            this.blowIv = HexStringToBytes("d9c7c3c8870d64bd");
        }

        public string Decrypt(string encryptedString)
        {
            if (encryptedString.Length == 32)
            {
                return DecryptTwelve(encryptedString);
            }
            else
            {
                return DecryptEleven(encryptedString);
            }
        }

        private string DecryptTwelve(string encryptedString)
        {
            byte[] encryptedData = HexStringToBytes(encryptedString.ToLower());
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = aesKey;
                aesAlg.IV = aesIv;
                aesAlg.Padding = PaddingMode.PKCS7;
                using (ICryptoTransform decryptor = aesAlg.CreateDecryptor())
                {
                    byte[] decryptedData = decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
                    return Encoding.UTF8.GetString(decryptedData).TrimEnd('\0');
                }
            }
        }

        private string DecryptEleven(string encryptedString)
        {
            byte[] encryptedData = HexStringToBytes(encryptedString.ToLower());
            int roundCount = encryptedData.Length / 8;
            int leftover = encryptedData.Length % 8;

            byte[] result = new byte[encryptedData.Length];
            byte[] currentVector = (byte[])blowIv.Clone();

            int offset = 0;
            for (int i = 0; i < roundCount; i++)
            {
                byte[] encryptedBlock = new byte[8];
                Array.Copy(encryptedData, i * 8, encryptedBlock, 0, 8);

                byte[] decryptedBlock = DecryptBlock(encryptedBlock);
                byte[] temp = XorBytes(decryptedBlock, currentVector);
                Array.Copy(temp, 0, result, offset, 8);
                currentVector = XorBytes(currentVector, encryptedBlock);
                offset += 8;
            }

            if (leftover > 0)
            {
                byte[] leftoverBlock = new byte[leftover];
                Array.Copy(encryptedData, encryptedData.Length - leftover, leftoverBlock, 0, leftover);

                byte[] encryptedBlock = EncryptBlock(currentVector);
                byte[] temp = XorBytes(leftoverBlock, encryptedBlock);
                Array.Copy(temp, 0, result, offset, leftover);
            }

            return Encoding.UTF8.GetString(result).TrimEnd('\0');
        }

        private byte[] DecryptBlock(byte[] block)
        {
            using (var blowfish = new Blowfish())
            {
                return blowfish.Decrypt(block, blowKey);
            }
        }

        private byte[] EncryptBlock(byte[] block)
        {
            using (var blowfish = new Blowfish())
            {
                return blowfish.Encrypt(block, blowKey);
            }
        }

        private byte[] XorBytes(byte[] a, byte[] b)
        {
            byte[] result = new byte[a.Length];
            for (int i = 0; i < a.Length; i++)
                result[i] = (byte)(a[i] ^ b[i]);
            return result;
        }

        private byte[] HexStringToBytes(string hex)
        {
            int len = hex.Length;
            byte[] bytes = new byte[len / 2];
            for (int i = 0; i < len; i += 2)
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            return bytes;
        }
    }

    public class Blowfish : IDisposable
    {
        private SymmetricAlgorithm _algorithm;

        public Blowfish()
        {
            _algorithm = new RijndaelManaged
            {
                KeySize = 128,
                BlockSize = 128,
                Mode = CipherMode.ECB,
                Padding = PaddingMode.None
            };
        }

        public byte[] Encrypt(byte[] data, byte[] key)
        {
            using (ICryptoTransform encryptor = _algorithm.CreateEncryptor(key, new byte[16]))
                return encryptor.TransformFinalBlock(data, 0, data.Length);
        }

        public byte[] Decrypt(byte[] data, byte[] key)
        {
            using (ICryptoTransform decryptor = _algorithm.CreateDecryptor(key, new byte[16]))
                return decryptor.TransformFinalBlock(data, 0, data.Length);
        }

        public void Dispose()
        {
            _algorithm?.Dispose();
        }
    }
}
